from pathlib import Path
import re
from rgit import do
from rgit import RGit
from rcfg import RCfg
from util import combine_path
import datetime
import subprocess
import binascii
from rui import RUi, ui_pretty_print
from rftp import RFtp
from util import do2
from subprocess import Popen
import os


class Rdb:
    var = {
        'MODEL': '${MODEL_NAME}',
        'ROOT': '${MGM_PROJECT_BASE_DIR}',
        'DISPLAY_MODEL_NAME': '${DISPLAY_MODEL_NAME}',
        'BOOT_REPO': '$(MGM_PROJECT_BASE_DIR)/boot/$(BOOT_PACKAGE)',
        'OS_REPO': '$(MGM_PROJECT_BASE_DIR)/os/$(OS_PACKAGE)',
        'SDK_REPO': '$(MGM_PROJECT_BASE_DIR)/sdk/$(SDK_PACKAGE)',
        'CORE_REPO': '$(MGM_PROJECT_BASE_DIR)/core/code',
        'CUSTOMER_REPO': '${CUSTOMER_ABSOLUTE_DIR}',
        'ISS_HW_VERSION': '${ISS_HW_VERSION}',
        'ISS_SW_VERSION': '${ISS_SW_VERSION}',
        'OUTPUT_IMAGE_DIR': '${IMAGE_ABSOLUTE_DIR}/${OUTPUT_IMAGE_DIR}',
        'OUTPUT_IMAGE': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}.img',
        'OUTPUT_ZOS2_IMAGE': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}.zos2',
        'OUTPUT_RFS2_IMAGE': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}.rfs2',
        'OUTPUT_BIN': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}.bin',
        'OUTPUT_HEX': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}.hex',
        'OUTPUT_BOOT_HEX': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}-BOOT.hex',
        'OUTPUT_OS_HEX': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}-OS.hex',
        'OUTPUT_FS_HEX': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}-FS.hex',
        'OUTPUT_ALL_HEX': '${SHOW_MODEL}ME-${ISS_HW_VERSION}-${ISS_SW_VERSION}-ALL.hex',
        'OUTPUT_STANDARD_MIB': 'STANDARD.mib',
        'OUTPUT_SNMP_MIB': 'STANDARD-SNMP.mib',
        'OUTPUT_PRIVATE_MIB': '${SHOW_MODEL}ME-${MIB_HW_VERSION}-${MIB_SW_VERSION}.mib',
        'OUTPUT_BULK_BIN': '${COMMON_BOARD_NAME}_${SHOW_MODEL}_${ISS_SW_VERSION}.img',
    }

    BOOT_REPO = 'BOOT_REPO'
    OS_REPO = 'OS_REPO'
    SDK_REPO = 'SDK_REPO'
    CORE_REPO = 'CORE_REPO'
    CUSTOMER_REPO = 'CUSTOMER_REPO'
    _REPO_DICT = {BOOT_REPO: BOOT_REPO, OS_REPO: OS_REPO, SDK_REPO: SDK_REPO,
                  CORE_REPO: CORE_REPO, CUSTOMER_REPO: CUSTOMER_REPO}

    release_re = re.compile('^release_info:')
    release_var_re = re.compile(r'(\S+)=(\S+)')
    cameotag_in_hex = '43414d454f544147'  # 'CAMEOTAG'
    non_backdoor_cmds = 'non_backdoor_cmds.csv'
    cmds_tail = '_cmds.csv'
    iss_exe = 'ISS.exe'
    release_note_tail = '_FW_Release Note.txt'

    def make_core_clean_all(self, log_file: str) -> Popen:
        cwd = os.getcwd()
        os.chdir(self._var_db['ROOT'])
        args = 'make core-clean all 1>/dev/null 2>' + log_file
        p = do2(args)
        os.chdir(cwd)
        return p

    def make_core_fw_all(self, log_file: str) -> Popen:
        cwd = os.getcwd()
        os.chdir(self._var_db['ROOT'])
        args = 'make core fw.all 1>/dev/null 2>' + log_file
        p = do2(args)
        os.chdir(cwd)
        return p

    def make_all(self, log_file: str) -> Popen:
        cwd = os.getcwd()
        os.chdir(self._var_db['ROOT'])
        args = 'make all 1>/dev/null 2>'+ log_file
        p = do2(args)
        os.chdir(cwd)
        return p

    def version_x_dot_xx(self) -> str:
        a = self._var_db['ISS_SW_VERSION'].split('-')
        return '.'.join(a[:len(a) - 1])

    def version_x0xx(self) -> str:
        return self._var_db['ISS_SW_VERSION'].split('-')[-1]

    def hex_prefix(self) -> str:
        return self._var_db['OUTPUT_HEX'].split('.')[0]

    def hex(self):
        return combine_path([
            self._var_db['OUTPUT_IMAGE_DIR'],
            self._var_db['OUTPUT_HEX']
        ])

    def mib(self):
        return combine_path([
            self._var_db['OUTPUT_IMAGE_DIR'],
            self._var_db['OUTPUT_PRIVATE_MIB']
        ])

    def all_hex(self):
        return combine_path([
            self._var_db['OUTPUT_IMAGE_DIR'],
            self._var_db['OUTPUT_ALL_HEX']
        ])

    def cmds(self):
        return combine_path([
            self._var_db['OUTPUT_IMAGE_DIR'],
            Rdb.non_backdoor_cmds
        ])

    def bin(self):
        return combine_path([
            self._var_db['OUTPUT_IMAGE_DIR'],
            self._var_db['OUTPUT_BIN'],
        ])

    def iss(self):
        return combine_path([
            self._var_db['OUTPUT_IMAGE_DIR'],
            Rdb.iss_exe
        ])

    @staticmethod
    def is_model(m) -> bool:
        p = Path(m)
        if p.is_dir() and p.joinpath('make.h').is_file() \
                and p.joinpath('env.h').is_file() \
                and p.joinpath('Makefile').is_file():
            return True
        else:
            return False

    def _append_cmd(self):
        make_file = Path(self._model).joinpath('Makefile')
        with open(make_file, 'r+') as f:
            content = f.readlines()
            for i in range(len(content) - 1, -1, -1):
                if Rdb.release_re.match(content[i]):
                    return
            f.write(self._cmd)

    def _gen_cmd(self, var_dict: dict = None):
        """ update var before gen_cmd if needed """
        if var_dict:
            self.var.update(var_dict)
        self._cmd = 'release_info:\n'
        for (k, v) in self.var.items():
            self._cmd += f'\techo "{k}={v}"\n'

    def _is_var_valid(self, var: str):
        return '#' not in var

    def _strip_release_info(self):
        args = ['make', '--no-print-directory', '-C', self._model,
                'release_info']
        c = do(args)
        # parse the completed output
        lines = str(c.stdout, 'utf-8').split('\n')
        for l in lines:
            m = Rdb.release_var_re.match(l)
            if m and self._is_var_valid(m.group(2)):
                if m.group(1) in self.var:
                    self._var_db[m.group(1)] = m.group(2)

    def __init__(self, model, var_dict, cfg: RCfg, ui: RUi):
        self._cmd = None
        if type(model) is Path:
            model = model.__str__()
        self._cfg = cfg
        self._ui = ui
        self._model = model
        self._var_db = {}

        self._gen_cmd(var_dict)
        self._append_cmd()
        self._strip_release_info()
        self._ftp_db = {'hex': '', 'all_hex': '', 'backup': '',
                        'last_release_note': ''}
        self._new_feature = ''
        self._bug_fix = ''
        self._repo_model = ('ENV', 'BOOT', 'OS', 'SDK', 'ICORE', 'CUS')
        self._gitinfo_inmake = ''
        self._checksum_runtime = ''
        self._checksum_prom = ''
        self._release_note = ''

        self.update_all_repos()

        self._local_hex_path = ''
        self._local_hex_all_path = ''
        self._local_backup_path = ''
        self._ui.ui_print_info('Detecting ftp path .......')
        self.sync_ftp_dirs()
        self._log_file = ''
        self._init_log()

    def _init_log(self):
        self._log_file = combine_path([
            Path(self._var_db['ROOT']).parent.__str__(),
            self._var_db['MODEL'] + '.log'
        ])

    def log_file_path(self) -> str:
        return self._log_file

    def set_local_hex_path(self, hex: str):
        self._local_hex_path = hex

    def set_local_hex_all_path(self, all: str):
        self._local_hex_all_path = all

    def set_local_backup_path(self, backup: str):
        self._local_backup_path = backup

    def local_hex_path(self):
        return self._local_hex_path

    def local_hex_all_path(self):
        return self._local_hex_all_path

    def local_backup_path(self):
        return self._local_backup_path

    def update_all_repos(self):
        ui_pretty_print(self.cur_repo())
        self._ui.ui_print_info(self.model())
        self._ui.ui_print_info('  +-> Update repo: ' + self._var_db['OS_REPO'])
        RGit(self._var_db['OS_REPO']).update()
        self._ui.ui_print_info('  +-> Update repo: ' + self._var_db['SDK_REPO'])
        RGit(self._var_db['SDK_REPO']).update()
        self._ui.ui_print_info(
            '  +-> Update repo: ' + self._var_db['CORE_REPO'])
        RGit(self._var_db['CORE_REPO']).update()
        self._ui.ui_print_info(
            '  +-> Update repo: ' + self._var_db['CUSTOMER_REPO'])
        RGit(self._var_db['CUSTOMER_REPO']).update()
        self._ui.ui_print_info(
            '  +-> Update repo: ' + self._var_db['BOOT_REPO'])
        RGit(self._var_db['BOOT_REPO']).update()

    def new_feature(self):
        return self._new_feature

    def bug_fix(self):
        return self._bug_fix

    def set_new_feature(self, n_f: str):
        self._new_feature = n_f

    def set_bug_fix(self, b_f: str):
        self._bug_fix = b_f

    def ftp_db(self) -> {}:
        return self._ftp_db

    def update_release_info(self):
        self._strip_release_info()

    def model(self) -> str:
        return self._var_db['MODEL']

    def root(self) -> str:
        return self._var_db['ROOT']

    def remote_br(self, repo: str) -> []:
        RGit(self._var_db[repo]).restore_worktree()
        RGit(self._var_db[repo]).pull()
        return [b['branch_name'] for b in
                RGit(self._var_db[repo]).branch(RGit.BRANCH_REMOTE)]

    def repo(self, repo: str):
        return self._var_db[repo]

    def cur_repo(self) -> {}:
        return {'BOOT_REPO': RGit(self._var_db['BOOT_REPO']).branch(
            RGit.BRANCH_CURRENT)[0]['branch_name'],
                'OS_REPO': RGit(self._var_db['OS_REPO']).branch(
                    RGit.BRANCH_CURRENT)[0]['branch_name'],
                'SDK_REPO': RGit(self._var_db['SDK_REPO']).branch(
                    RGit.BRANCH_CURRENT)[0]['branch_name'],
                'CORE_REPO': RGit(self._var_db['CORE_REPO']).branch(
                    RGit.BRANCH_CURRENT)[0]['branch_name'],
                'CUSTOMER_REPO': RGit(self._var_db['CUSTOMER_REPO']).branch(
                    RGit.BRANCH_CURRENT)[0]['branch_name']}

    def iss_hw_ver(self) -> str:
        return self._var_db['ISS_HW_VERSION']

    def iss_sw_ver(self) -> str:
        return self._var_db['ISS_SW_VERSION']

    @staticmethod
    def ver_2_last_ver(ver: str) -> str:
        g = re.match(r'([a-zA-Z]*[0]*)([0-9]*)', ver)
        x0xx = g.group(1) + (
                    '%*s' % (len(g.group(2)), int(g.group(2)) - 1)).replace(' ',
                                                                            '0')
        return x0xx

    def ftp_model_name(self, ver_x_dot_xx: str = None) -> str:
        if ver_x_dot_xx is None:
            ver_x_dot_xx = self.version_x_dot_xx()
        d_n = self._var_db['DISPLAY_MODEL_NAME']
        h_v = self._var_db['ISS_HW_VERSION']
        if ver_x_dot_xx == '20.04':
            """ TODO: 52-C1 """
            ...
        elif ver_x_dot_xx in ('10.05', '6.11'):
            return d_n + '-ME_' + h_v
        elif ver_x_dot_xx == '6.14':
            return d_n + '-ME'
        elif ver_x_dot_xx == '7.03' and 'X' not in d_n and 'TS' not in d_n:
            return d_n + '-ME-' + h_v
        elif ver_x_dot_xx == '7.03' and ('X' in d_n or 'TS' in d_n):
            return d_n + '-ME'

    def ftp_last_release_note(self) -> str:
        x0xx = self.version_x0xx()
        ftp = RFtp(self._cfg.ftp_host())
        ftp.login(self._cfg.ftp_user(), self._cfg.ftp_passwd())
        while True:
            x0xx = self.ver_2_last_ver(x0xx)
            ftp_last_release_note = combine_path([
                self.ftp_model_dir(x0xx=x0xx),
                # self.ftp_old_model_dir(x0xx=x0xx),
                self.hex_prefix().replace(
                    self.version_x0xx(),
                    x0xx
                ) + Rdb.release_note_tail
            ])
            if ftp.is_file_exists(ftp_last_release_note):
                ftp.close()
                return ftp_last_release_note

    def ftp_last_model_dir(self, x0xx_c: str = None) -> str:
        """ TODO: may not be accurate """
        if x0xx_c is None:
            x0xx_c = self.version_x0xx()
        dmn_a = self._var_db['DISPLAY_MODEL_NAME'].split('-')
        dmn = '-'.join(dmn_a[:len(dmn_a) - 1])
        me_token = '_ME_'
        v_a = self._var_db['ISS_SW_VERSION'].split('-')
        v_token = 'v' + '.'.join(v_a[0:len(v_a) - 1]) + '.' + x0xx_c[0] + 'xxx'
        x0xx = self.ver_2_last_ver(x0xx_c)
        if self._var_db['DISPLAY_MODEL_NAME'] == 'DES-1210-52':
            """ TODO: DES-1210-52 is special """
            base_dir = 'DES-1210-52ME_' + v_token
            return combine_path([
                self._cfg.ftp_fw(), x0xx, base_dir
            ])
        else:
            base_dir = dmn + me_token + v_token
            return combine_path([
                self._cfg.ftp_fw(), x0xx, base_dir,
                self.ftp_model_name()
            ])

    def ftp_model_dir(self, x0xx: str = None) -> str:
        """ TODO: may not be accurate """
        if x0xx is None:
            x0xx = self.version_x0xx()
        dmn_a = self._var_db['DISPLAY_MODEL_NAME'].split('-')
        dmn = '-'.join(dmn_a[:len(dmn_a) - 1])
        me_token = '_ME_'
        v_a = self._var_db['ISS_SW_VERSION'].split('-')
        v_token = 'v' + '.'.join(v_a[0:len(v_a) - 1]) + '.' + x0xx[0] + 'xxx'
        if self._var_db['DISPLAY_MODEL_NAME'] == 'DES-1210-52':
            """ TODO: DES-1210-52 is special """
            base_dir = 'DES-1210-52ME_' + v_token
            return combine_path([
                self._cfg.ftp_fw(),
                x0xx,
                base_dir
            ])
        else:
            base_dir = dmn + me_token + v_token
            return combine_path([
                self._cfg.ftp_fw(), x0xx,
                base_dir, self.ftp_model_name()
            ])

    def ftp_old_model_dir(self, x0xx: str = None) -> str:
        """ TODO: may not be accurate """
        if x0xx is None:
            x0xx = self.version_x0xx()
        dmn_a = self._var_db['DISPLAY_MODEL_NAME'].split('-')
        dmn = '-'.join(dmn_a[:len(dmn_a) - 1])
        me_token = '_ME_'
        v_a = self._var_db['ISS_SW_VERSION'].split('-')
        v_token = 'v' + '.'.join(v_a[0:len(v_a) - 1]) + '.' + x0xx[0] + 'xxx'
        if self._var_db['DISPLAY_MODEL_NAME'] == 'DES-1210-52':
            """ TODO: DES-1210-52 is special """
            base_dir = 'DES-1210-52ME_' + v_token
            return combine_path([
                '/v1/Project/Switch/DLINK-ME/Firmware',
                x0xx,
                base_dir
            ])
        else:
            base_dir = dmn + me_token + v_token
            return combine_path([
                '/v1/Project/Switch/DLINK-ME/Firmware', x0xx,
                base_dir, self.ftp_model_name()
            ])

    def ftp_backup_dir(self) -> str:
        """ TODO: may not be accurate """
        if self._var_db['DISPLAY_MODEL_NAME'] == 'DES-1210-52':
            """ TODO: DES-1210-52 is special """
            ...
        dmn_a = self._var_db['DISPLAY_MODEL_NAME'].split('-')
        dmn = '-'.join(dmn_a[:len(dmn_a) - 1])
        me_token = '_ME_'
        v_a = self._var_db['ISS_SW_VERSION'].split('-')
        v_token = 'v' + '.'.join(v_a[0:len(v_a) - 1]) + '.' + v_a[-1][0] + 'xxx'
        if self._var_db['DISPLAY_MODEL_NAME'] == 'DES-1210-52':
            """ TODO: DES-1210-52 is special """
            base_dir = 'DES-1210-52ME_' + v_token
            return combine_path([
                self._cfg.ftp_fw_backup(),
                self.iss_sw_ver().split('-')[-1],
                base_dir
            ])
        else:
            base_dir = dmn + me_token + v_token
            return combine_path([
                self._cfg.ftp_fw_backup(), self.iss_sw_ver().split('-')[-1],
                base_dir, self.ftp_model_name()
            ])

    def ftp_model_all_dir(self) -> str:
        """ TODO: may not be accurate """
        dxs = self._var_db['DISPLAY_MODEL_NAME'].split('-')[0]
        v_a = self._var_db['ISS_SW_VERSION'].split('-')
        v_token = '-'.join(v_a[0:len(v_a) - 1]) + '-' + v_a[-1][0]
        if dxs == 'DGS' and v_token == '6-14-E':
            v_token += 'xxx'
        else:
            v_token += 'xx'
        base_dir = dxs + '-' + v_token
        return combine_path(
            [self._cfg.ftp_fw(), self.iss_sw_ver().split('-')[-1], 'AllHEX',
             base_dir])

    def sync_ftp_dirs(self):
        self.set_ftp_db_hex(self.ftp_model_dir())
        self.set_ftp_db_all_hex(self.ftp_model_all_dir())
        self.set_ftp_db_backup(self.ftp_backup_dir())
        self.set_ftp_db_last_release(self.ftp_last_release_note())

    @staticmethod
    def ftp_db_var_check(k: str, v: str) -> bool:
        """TODO: should do ftp var check, check if exists or not"""
        return True

    def set_ftp_db_hex(self, model_dir: str):
        self._ftp_db['hex'] = model_dir

    def ftp_db_hex(self):
        return self._ftp_db['hex']

    def set_ftp_db_all_hex(self, all_dir: str):
        self._ftp_db['all_hex'] = all_dir

    def ftp_db_all_hex(self):
        return self._ftp_db['all_hex']

    def set_ftp_db_backup(self, backup_dir: str):
        self._ftp_db['backup'] = backup_dir

    def ftp_db_backup(self):
        return self._ftp_db['backup']

    def set_ftp_db_last_release(self, last_release_path: str):
        self._ftp_db['last_release_note'] = last_release_path

    def ftp_db_last_release(self) -> str:
        return self._ftp_db['last_release_note']

    def init_gitinfo_inmake(self):
        sh = 'make -C %s git-info --no-print-directory' % self._model
        (status, output) = subprocess.getstatusoutput(sh)
        # by the way, update git_info in model_params dict
        lines = output.split('\n')
        git_info = ''
        git_info += '(model)  (repertory)          (branch)             (version)      (commit by)      (date)\r\n'
        for l in lines:
            larr = l.split()
            if larr[0] in self._repo_model:
                git_info += ' %-8s %-20s%-21s%-16s%-16s%-s\r\n' % (
                    larr[0], larr[1], larr[2], larr[3][5:], larr[4][5:],
                    ('<' + re.search(r'(.*)<(.*)>(.*)', l).group(2) + '>'))
        self._gitinfo_inmake = git_info

    def gen_checksum(self):
        # extract prom and runtime checksum from hex and boot hex
        hex = combine_path(
            [self._var_db['OUTPUT_IMAGE_DIR'], self._var_db['OUTPUT_HEX']])
        boot_hex = combine_path([
            self._var_db['OUTPUT_IMAGE_DIR'],
            self._var_db['OUTPUT_BOOT_HEX']
        ])
        cameo_tag = Rdb.cameotag_in_hex
        # runtime checksum
        with open(str(hex), 'rb') as bf:
            str_out = binascii.b2a_hex(bf.read()).decode()
            goto = str_out.find(cameo_tag) + 32
            str_out_left = str_out[goto:]
            cameo_tag_start = str_out_left.find(cameo_tag) + len(cameo_tag) + 8
            cameo_tag_end = cameo_tag_start + 8
            runtime_checksum = str_out_left[cameo_tag_start:cameo_tag_end]
            self._checksum_runtime = '0x' + runtime_checksum
        # prom_checksum
        with open(str(boot_hex), 'rb') as bf:
            str_out = binascii.b2a_hex(bf.read()).decode()
            cameo_tag_start = \
                str_out.find(cameo_tag) + len(cameo_tag) + 8
            cameo_tag_end = cameo_tag_start + 8
            prom_checksum = str_out[cameo_tag_start:cameo_tag_end]
            self._checksum_prom = '0x' + prom_checksum

    def release_note(self) -> []:
        text = []
        today = datetime.date.today()
        text.append(
            '________________________________________________________________________________\r\n')
        text.append(
            '        RELEASE VERSION  : %s\r\n' % (
                self._var_db['ISS_SW_VERSION'].replace('-', '.')))
        text.append('        RELEASE DATE     : %04d/%02d/%02d\r\n' % (
            today.year, today.month, today.day))
        text.append(
            '        RELEASEE BY      : %s\r\n' % (self._cfg.releaser()))
        text.append(
            '________________________________________________________________________________\r\n\r\n')

        text.append('[CHECK-CODE]    : \r\n')
        text.append('[CHECKSUM]      : [N/A]\r\n')
        text.append('[PART-NO]       : [N/A]\r\n')
        text.append('[GIT]           :\r\n%s\r\n\r\n' % self._gitinfo_inmake)

        text.append('[TMPL-VER]      : [N/A]\r\n')
        text.append('[ECN-NO]        : [N/A]\r\n')
        text.append('[TP-TIME]       : [N/A]\r\n')
        text.append('[FW-VER]        : %s\r\n' % (
            self._var_db['ISS_SW_VERSION'].replace('-', '.')))
        text.append('[PLM-NO]        : [N/A]\r\n')
        text.append(
            '________________________________________________________________________________\r\n\r\n')

        text.append('New Feature:\r\n')
        text.append('-------------\r\n')
        text.append('%s\r\n\r\n' % (self._new_feature.replace('\n', '\r\n')))

        text.append('Bug Fixed:\r\n')
        text.append('-------------\r\n')
        text.append('%s\r\n\r\n' % (self._bug_fix.replace('\n', '\r\n')))

        text.append('Changed:\r\n')
        text.append('-------------\r\n')
        text.append('N/A\r\n\r\n')

        text.append('Note:\r\n')
        text.append('-------------\r\n\r\n')

        text.append('Version :\r\n')
        text.append('    HW      : %s\r\n' % (self._var_db['ISS_HW_VERSION']))
        text.append('    PROM    : 1.01.033\r\n')
        text.append('    Runtime : %s\r\n\r\n' % (
            self._var_db['ISS_SW_VERSION'].replace('-', '.')))

        text.append('Checksum :\r\n')
        text.append('    PROM    : %s\r\n' % self._checksum_prom)
        text.append('    Runtime : %s\r\n\r\n' % self._checksum_runtime)

        text.append('Known issues:\r\n')
        text.append('-------------\r\n\r\n')
        self._release_note = text
        return text
